﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;

namespace color2gray
{
    class constants
    { 
        public double[] angles = new double[] { 0, Math.PI / 4, Math.PI / 2, 3 * Math.PI / 4, Math.PI, 5 * Math.PI / 4, 3 * Math.PI / 2, 7 * Math.PI / 4, 2 * Math.PI };    
    }

    struct CIELAB
    {
        private const double Xn = 0.950456;
        private const double Yn = 1.0;
        private const double Zn = 1.088754;

        public double L;
        public double a;
        public double b;

        public CIELAB(Double _L, Double _a, Double _b)
        {
            L = _L;
            a = _a;
            b = _b;
        }

        public CIELAB(Color rgb)
        {
            CIELAB lab = Parse(rgb);
            L = lab.L;
            a = lab.a;
            b = lab.b;
        }

        public CIELAB(CIEXYZ xyz)
        {
            double delta = 6.0 / 29.0;

            double fx = xyz.X / Xn > Math.Pow(delta, 3) ? Math.Pow(xyz.X / Xn, 1.0 / 3.0) :
                (1.0 / 3.0) * Math.Pow((1 / delta), 2) * xyz.X / Xn + (4.0 / 29.0);

            double fy = xyz.Y / Yn > Math.Pow(delta, 3) ? Math.Pow(xyz.Y / Yn, 1.0 / 3.0) :
                (1.0 / 3.0) * Math.Pow((1 / delta), 2) * xyz.Y / Yn + (4.0 / 29.0);

            double fz = xyz.Z / Zn > Math.Pow(delta, 3) ? Math.Pow(xyz.Z / Zn, 1.0 / 3.0) :
                (1.0 / 3.0) * Math.Pow((1 / delta), 2) * xyz.Z / Zn + (4.0 / 29.0);

            L = 116.0 * fy - 16.0;
            a = 500.0 * (fx - fy);
            b = 200.0 * (fy - fz);
        }

       

 
        public CIEXYZ ToCIEXyz()
        {
            double delta = 6.0 / 29.0;
            double fy = (L + 16.0) / 116.0;
            double fx = fy + a / 500.0;
            double fz = fy - b / 200.0;

            double X = fx > delta ? Xn * Math.Pow(fx, 3) : (fx - 16.0 / 116.0) * 3 * Math.Pow(delta, 2);
            double Y = fy > delta ? Yn * Math.Pow(fy, 3) : (fy - 16.0 / 116.0) * 3 * Math.Pow(delta, 2);
            double Z = fz > delta ? Zn * Math.Pow(fz, 3) : (fz - 16.0 / 116.0) * 3 * Math.Pow(delta, 2);

            return new CIEXYZ(X, Y, Z);
        }

        public static CIELAB Parse(CIEXYZ xyz)
        {
            CIELAB lab;

            lab.L = 116.0 * Fxyz(xyz.Y / Yn) - 16;
            lab.a = 500.0 * (Fxyz(xyz.X / Xn) - Fxyz(xyz.Y / Yn));
            lab.b = 200.0 * (Fxyz(xyz.Y / Yn) - Fxyz(xyz.Z / Zn));

            int ls = (int)(2.55 * lab.L + 4);
            int As = (int)(lab.a + 4);
            int Bs = (int)(lab.b + 4);

            lab.L = ls;
            lab.a = As;
            lab.b = Bs;

            return lab;
        }
 
        private static double Fxyz(double t)
        {
            return ((t > 0.008856) ? Math.Pow(t, (1.0 / 3.0)) : (7.787 * t + 16.0 / 116.0));
        }

        public static CIELAB Parse(Color col)
        {
            CIEXYZ xyz = CIEXYZ.Parse(col);
            return CIELAB.Parse(xyz);
        }
 
        public override string ToString()
        {
            return "CIELab [L*=" + L.ToString("F") + ", a*=" + a.ToString("F") + ", b*=" + b.ToString("F") + "]";
        }

    }

    struct CIEXYZ
    {
        public double X;
        public double Y;
        public double Z;

        public CIEXYZ(Double _X, Double _Y, Double _Z)
        {
            X = _X;
            Y = _Y;
            Z = _Z;
        }

        public CIEXYZ(Color rgb)
        {
            CIEXYZ xyz = Parse(rgb);

            X = xyz.X;
            Y = xyz.Y;
            Z = xyz.Z;
        }

        public CIEXYZ(CIELAB Lab)
        {
            CIEXYZ xyz = Lab.ToCIEXyz();
            X = xyz.X;
            Y = xyz.Y;
            Z = xyz.Z;
        }

        public static CIEXYZ Parse(Color col)
        {
            // normalize red, green, blue values  
            double rLinear = (double)col.R / 255.0;
            double gLinear = (double)col.G / 255.0;
            double bLinear = (double)col.B / 255.0;

            // convert to a sRGB form  
            double r = (rLinear > 0.04045) ? Math.Pow((rLinear + 0.055) / (
                1 + 0.055), 2.2) : (rLinear / 12.92);
            double g = (gLinear > 0.04045) ? Math.Pow((gLinear + 0.055) / (
                1 + 0.055), 2.2) : (gLinear / 12.92);
            double b = (bLinear > 0.04045) ? Math.Pow((bLinear + 0.055) / (
                1 + 0.055), 2.2) : (bLinear / 12.92);

            // converts  
            return new CIEXYZ(
                (r * 0.4124 + g * 0.3576 + b * 0.1805),
                (r * 0.2126 + g * 0.7152 + b * 0.0722),
                (r * 0.0193 + g * 0.1192 + b * 0.9505)
                );
        }

        public CIELAB ToCIELab()
        {
            return new CIELAB(new CIEXYZ(X, Y, Z));
        }

        public override string ToString()
        {
            return "CIEXyz [X=" + X.ToString("F") + ", Y=" + Y.ToString("F") + ", Z=" + Z.ToString("F") + "]";
        }
    }  

    
    class Color2Gray
    {
        private double crunch(double chrom_dist, float alphaL)
        {
            return alphaL == 0 ? 0 : alphaL * Math.Tanh(chrom_dist / alphaL);
        }

        private double calcDelta(int ia, int ja, int ib, int jb, float thetaL, float alphaL, double[,] L, double[,] A, double[,] B)
        {
            double dL = L[ia, ja] - L[ib, jb];
            double dC = crunch(Math.Sqrt(Math.Pow(A[ia, ja] - A[ib, jb], 2) + Math.Pow(B[ia, ja] - B[ib, jb], 2)), alphaL);

            if (Math.Abs(dL) > dC)
                return dL;
            else
                return dC * ((Math.Cos(thetaL) * (A[ia, ja] - A[ib, jb]) + Math.Sin(thetaL) * (B[ia, ja] - B[ib, jb])) >= 0 ? 1 : -1);
        }

        double[,] resultPixelsDistance(int r, float thetaL, float alphaL, Bitmap source, double[,]L, double[,]A, double[,]B) 
        {
            double[,] matrix = new double[source.Width, source.Height];

            for (int i = 0; i < source.Width; i++)
            {
                for (int j = 0; j < source.Height; j++)
                {
                    matrix[i, j] = 0;

                }
            }


            int x, y;
            for (x = 0; x < source.Width; x++)
            {
                for (y = 0; y < source.Height; y++)
                {
                    int xx, yy;

                    for (xx = x - r; xx <= x + r; xx++)
                    {
                        if (xx < 0 || xx >= source.Width) continue;

                        for (yy = y - r; yy <= y + r; yy++)
                        {
                            if (yy >= source.Height || yy < 0) continue;

                            double delta = calcDelta(x, y, xx, yy, thetaL, alphaL, L, A, B);

                            matrix[x, y] += delta;
                            matrix[xx, yy] -= delta;

                        }
                    }
                }
            }


            return matrix;
        }

        private Bitmap solve_data(double[,] matrix, int r, Bitmap source)
        {
            const int iterations = 10;
            int k, x, y;

            for (k = 0; k < iterations; k++)
            {

                for (x = 0; x < source.Width; x++)
                {
                    for (y = 0; y < source.Height; y++)
                    {
                        float sum = 0;
                        int count = 0;
                        int xx, yy;

                        for (xx = x - r; xx <= x + r; xx++)
                        {
                            if (xx < 0 || xx >= source.Width) continue;
                            for (yy = y - r; yy <= y + r; yy++)
                            {
                                if (yy >= source.Height || yy < 0) continue;
                                Color c = source.GetPixel(xx, yy);
                                sum += (float)c.R;
                                count++;
                            }
                        }
                        double value = (matrix[x,y] + sum) / (float)count;

                        if (value > 255) value = 255;
                        if (value < 0) value = 0;
                        source.SetPixel(x, y, Color.FromArgb((int)value, (int)value, (int)value));

                    }
                }
            }
            return source;
        }       

        public Image BrightnessCorrection(Image source)
        {
            int x, y;
            float min, max, mean;
            Color c;
            float s, b;
            float[,] gray;
            Bitmap bm = new Bitmap(source);
            gray = new float[bm.Width, bm.Height];
            min = 100;
            max = -100;
            mean = 0;
            for (y = 0; y < bm.Height; y++)
            {
                for (x = 0; x < bm.Width; x++)
                {
                    c = bm.GetPixel(x, y);
                    s = c.GetSaturation();
                    b = c.GetBrightness();
                    if (s == 0)
                    {
                        gray[x, y] = (float)1.5 * b;
                    }
                    else
                    {
                        gray[x, y] = b + b * s;
                    }
                    if (gray[x, y] < min)
                    {
                        min = gray[x, y];
                    }
                    if (gray[x, y] > max)
                    {
                        max = gray[x, y];
                    }
                    mean += gray[x, y];
                }
            }
            mean /= (float)(bm.Height * bm.Width);
            min = 0; max = (float)((mean + max) * 0.5);
            Bitmap result = new Bitmap(source);
            for (y = 0; y < result.Height; y++)
            {
                for (x = 0; x < result.Width; x++)
                {
                    b = (float)229.5 * (gray[x, y] - min) / (max - min);
                    if (b > 255)
                    {
                        b = 255;
                    }
                    if (b < 0) 
                    { 
                        b = 0; 
                    }
                    result.SetPixel(x, y, Color.FromArgb((int)b, (int)b, (int)b));
                }
            }

            return (Image)result;
        }
        public Image applyColor2gray(Image picture, int r, int alpha, int theta, bool adjust)
        {
            constants cons = new constants();
            Bitmap newPicture = new Bitmap(picture);
            Bitmap PictureAux = new Bitmap(picture);
            double[,] L = new double[newPicture.Width, newPicture.Height];
            double[,] A = new double[newPicture.Width, newPicture.Height];
            double[,] B = new double[newPicture.Width, newPicture.Height];

            for (int i = 0; i < newPicture.Width; i++)
            {
                for (int j = 0; j < newPicture.Height; j++)
                {
                    Color c = newPicture.GetPixel(i, j);
                    CIELAB LAB = CIELAB.Parse(c);

                    L[i, j] = LAB.L;
                    A[i, j] = LAB.a;
                    B[i, j] = LAB.b;

                    if (LAB.L > 255) LAB.L = 255;
                    newPicture.SetPixel(i, j, Color.FromArgb((int)LAB.L, (int)LAB.L, (int)LAB.L));
                }
            }

            double[,] matrix = new double[newPicture.Width, newPicture.Height];
            matrix = resultPixelsDistance(r, (int)cons.angles[theta], alpha, newPicture, L, A, B);

            newPicture = solve_data(matrix, 2, newPicture);
            if (adjust == true)
            {
                PictureAux = (Bitmap)BrightnessCorrection(picture);

                for (int i = 0; i < newPicture.Width; i++)
                {
                    for (int j = 0; j < newPicture.Height; j++)
                    {
                        Color c = newPicture.GetPixel(i, j);
                        Color c2 = PictureAux.GetPixel(i, j);
                        int newC = (c2.R + c.R) / 2;
                        newPicture.SetPixel(i, j, Color.FromArgb(newC, newC, newC));
                    }
                }
            }
            return newPicture;
        }


    }
}
